﻿using Autodesk.Revit.DB;
using System;
using System.Collections.Generic;
using System.Linq;

namespace CoreDB.Extensions
{
    /// <summary>
    /// 用于在模型中绘制线
    /// </summary>
    public static class DrawExtension
    {
        #region DrawCurve
        public static List<ElementId> Draw(this Curve c, Document doc, bool drawDirection = false)
        {
            return c.Draw(new DrawOption { Document = doc, DrawLineArrow = drawDirection });
        }
        public static List<ElementId> Draw(this IEnumerable<Curve> cs, Document doc, bool drawDirection = false)
        {
            var drawOption = new DrawOption { Document = doc, DrawLineArrow = drawDirection };
            return cs.Draw(drawOption);
        }
        public static List<ElementId> Draw(this IEnumerable<Curve> cs, DrawOption drawOption)
        {
            List<ElementId> result = new List<ElementId>();
            foreach (var c in cs)
            {
                var drawIds = c.Draw(drawOption);
                if (drawIds.Count > 0)
                    result.AddRange(drawIds);
            }
            return result;
        }
        /// <summary>
        /// 绘制指定的线段
        /// Curve
        /// {
        ///    Arc, Ellipse 有 Normal 属性
        ///    Line 有 Direction 属性
        ///    HermiteSpLine, NurbSpline 有CtrlPoints属性
        /// }
        /// </summary>
        /// <param name="line"></param>
        /// <param name="option">绘制选项</param>
        /// <returns></returns>
        public static List<ElementId> Draw(this Curve curve, DrawOption option)
        {
            var result = new List<ElementId>();
            //Line targetLine;
            Plane mainPlane;
            CurveElement curveElement;
            Document doc;

            if (option.View == null)
            {
                if (option.Document == null)
                    return result;

                doc = option.Document;
                CaculatePlane(curve, out XYZ curveCenter, out XYZ curveNormal);

                mainPlane = Plane.CreateByNormalAndOrigin(curveNormal, curveCenter);
                curveElement = doc.Create.NewModelCurve(curve, SketchPlane.Create(doc, mainPlane));
            }
            else
            {
                doc = option.View.Document;
                mainPlane = option.View.SketchPlane?.GetPlane();

                curveElement = doc.Create.NewDetailCurve(option.View, curve);
            }

            result.Add(curveElement.Id);

            if (option.LineStyle != null)
                curveElement.LineStyle = option.LineStyle;

            if (option.DrawLineArrow)
            {
                if (curve is Line line)
                {
                    var offsetDir = mainPlane.Normal.CrossProduct(line.Direction).Normalize();
                    var arrowScale = option.LineArrowScale;

                    if (line.Length <= 2 * arrowScale)
                        arrowScale = line.Length / 4;

                    var arrowCenter = line.Center();
                    if (option.DrawArrowOnEnd)
                        arrowCenter = line.GetEndPoint(1).Offset(-line.Direction, arrowScale);

                    var arrowHeadPoint = arrowCenter.Offset(line.Direction, arrowScale);
                    var arrowTopPoint = arrowCenter.Offset(offsetDir, arrowScale);
                    var arrowBottomPoint = arrowCenter.Offset(-offsetDir, arrowScale);

                    CurveElement arrowTop = null;
                    CurveElement arrowBottom = null;
                    if (option.View != null)
                    {
                        arrowTop = doc.Create.NewDetailCurve(option.View, Line.CreateBound(arrowHeadPoint, arrowTopPoint));
                        arrowBottom = doc.Create.NewDetailCurve(option.View, Line.CreateBound(arrowHeadPoint, arrowBottomPoint));
                    }
                    else
                    {
                        var arrowHeadTopLine = Line.CreateBound(arrowHeadPoint, arrowTopPoint);
                        if (arrowHeadTopLine != null)
                            arrowTop = doc.Create.NewModelCurve(arrowHeadTopLine, SketchPlane.Create(doc, mainPlane));

                        var arrowHeadBottomLine = Line.CreateBound(arrowHeadPoint, arrowBottomPoint);
                        if (arrowHeadBottomLine != null)
                            arrowBottom = doc.Create.NewModelCurve(arrowHeadBottomLine, SketchPlane.Create(doc, mainPlane));
                    }

                    if (option.LineStyle != null)
                    {
                        if (arrowTop != null)
                            arrowTop.LineStyle = option.LineStyle;
                        if (arrowBottom != null)
                            arrowBottom.LineStyle = option.LineStyle;
                    }

                    if (arrowTop != null)
                        result.Add(arrowTop.Id);
                    if (arrowBottom != null)
                        result.Add(arrowBottom.Id);
                }
                else if (curve is Arc arc)
                {
                    var vertices = arc.Vertices();
                    XYZ arrowCenter = vertices.ElementAtOrDefault(vertices.Count / 2);
                    XYZ direction = (vertices.ElementAtOrDefault(vertices.Count / 2 + 1) - arrowCenter).Normalizex();

                    if (option.DrawArrowOnEnd)
                    {
                        arrowCenter = vertices.FirstOrDefault();
                        direction = (vertices.ElementAtOrDefault(1) - arrowCenter).Normalizex();
                    }
                    var offsetDir = mainPlane.Normal.CrossProduct(direction).Normalize();
                    var arrowScale = option.LineArrowScale;

                    var arrowHeadPoint = arrowCenter.Offset(direction, arrowScale);
                    var arrowTopPoint = arrowCenter.Offset(offsetDir, arrowScale);
                    var arrowBottomPoint = arrowCenter.Offset(-offsetDir, arrowScale);

                    CurveElement arrowTop = null;
                    CurveElement arrowBottom = null;
                    if (option.View != null)
                    {
                        arrowTop = doc.Create.NewDetailCurve(option.View, Line.CreateBound(arrowHeadPoint, arrowTopPoint));
                        arrowBottom = doc.Create.NewDetailCurve(option.View, Line.CreateBound(arrowHeadPoint, arrowBottomPoint));
                    }
                    else
                    {
                        var arrowHeadTopLine = Line.CreateBound(arrowHeadPoint, arrowTopPoint);
                        if (arrowHeadTopLine != null)
                            arrowTop = doc.Create.NewModelCurve(arrowHeadTopLine, SketchPlane.Create(doc, mainPlane));

                        var arrowHeadBottomLine = Line.CreateBound(arrowHeadPoint, arrowBottomPoint);
                        if (arrowHeadBottomLine != null)
                            arrowBottom = doc.Create.NewModelCurve(arrowHeadBottomLine, SketchPlane.Create(doc, mainPlane));
                    }

                    if (option.LineStyle != null)
                    {
                        if (arrowTop != null)
                            arrowTop.LineStyle = option.LineStyle;
                        if (arrowBottom != null)
                            arrowBottom.LineStyle = option.LineStyle;
                    }

                    if (arrowTop != null)
                        result.Add(arrowTop.Id);
                    if (arrowBottom != null)
                        result.Add(arrowBottom.Id);
                }


            }

            return result;
        }

        #endregion

        #region DrawPoint
        public static List<ElementId> Draw(this XYZ point, Document doc)
        {
            return point.Draw(new DrawOption { Document = doc });
        }
        /// <summary>
        /// 绘制指定的点
        /// </summary>
        /// <param name="point"></param>
        /// <param name="option"></param>
        /// <returns></returns>
        public static List<ElementId> Draw(this XYZ point, DrawOption option)
        {
            var result = new List<ElementId>();

            if (option.View == null)
            {
                if (option.Document == null)
                    return result;

                var doc = option.Document;
                foreach (var axis in new XYZ[] { XYZ.BasisX, XYZ.BasisY, XYZ.BasisZ })
                {
                    var plane = Plane.CreateByNormalAndOrigin(axis, point);
                    var arc = Arc.Create(plane, option.PointOutlineRadius, 0, Math.PI * 2);
                    var modelArc = doc.Create.NewModelCurve(arc, SketchPlane.Create(doc, plane));
                    if (option.LineStyle != null)
                    {
                        modelArc.LineStyle = option.LineStyle;
                    }
                    result.Add(modelArc.Id);
                }
            }
            else
            {
                var doc = option.View.Document;

                var plane = option.View.SketchPlane.GetPlane();
                var targetPoint = point.Flat(option.View.GenLevel.Elevation);

                var mainArc = Arc.Create(targetPoint, option.PointOutlineRadius, 0, Math.PI * 2, plane.XVec, plane.YVec);
                var mainDetailArc = doc.Create.NewModelCurve(mainArc, option.View.SketchPlane);

                if (option.LineStyle != null)
                {
                    mainDetailArc.LineStyle = option.LineStyle;
                }

                result.Add(mainDetailArc.Id);
            }

            return result;
        }
        public static List<List<ElementId>> Draw(this IEnumerable<XYZ> points, Document doc)
        {
            List<List<ElementId>> result = new List<List<ElementId>>();
            var drawOption = new DrawOption { Document = doc };
            foreach (var point in points)
                result.Add(point.Draw(drawOption));
            return result;
        }
        #endregion

        #region DrawBoundingBox & Outline
        public static List<ElementId> Draw(this BoundingBoxXYZ box, DrawOption option)
        {
            return box.GetEdges(FaceDirection.All).Draw(option);
        }
        public static List<ElementId> Draw(this BoundingBoxXYZ box, Document doc, bool drawDirection = false)
        {
            DrawOption drawOption = new DrawOption() { Document = doc, DrawLineArrow = drawDirection };
            return box.Draw(drawOption);
        }
        public static List<ElementId> Draw(this Outline ot, DrawOption option)
        {
            return ot.GetEdges(FaceDirection.All).Draw(option);
        }
        public static List<ElementId> Draw(this Outline ot, Document doc, bool drawDirection = false)
        {
            DrawOption drawOption = new DrawOption() { Document = doc, DrawLineArrow = drawDirection };
            return ot.Draw(drawOption);
        }
        #endregion



















        private static void CaculatePlane(Curve curve, out XYZ curveCenter, out XYZ normalDir)
        {
            if (curve is Line line)
            {
                curveCenter = line.Center();
                XYZ rightDir;
                if (line.Direction.IsParallel(XYZ.BasisZ, 0.01))
                    rightDir = line.Direction.CrossProduct(XYZ.BasisX).Normalizex();
                else
                    rightDir = line.Direction.CrossProduct(XYZ.BasisZ).Normalizex();
                normalDir = rightDir.CrossProduct(line.Direction).Normalizex();
            }
            else if (curve is Arc arc)
            {
                curveCenter = arc.Center;
                normalDir = arc.Normal;
            }
            else if (curve is Ellipse ellipse)
            {
                curveCenter = ellipse.Center;
                normalDir = ellipse.Normal;
            }
            else if (curve is HermiteSpline hSpline)
            {
                curveCenter = hSpline.ControlPoints.AveragePoint();
                XYZ tempDir = (hSpline.ControlPoints.ElementAtOrDefault(1) - hSpline.ControlPoints.ElementAtOrDefault(0)).Normalizex();
                XYZ rightDir = tempDir.IsParallel(XYZ.BasisZ, 0.01) ? XYZ.BasisX : XYZ.BasisZ;
                normalDir = tempDir.CrossProduct(rightDir).CrossProduct(tempDir).Normalizex();
            }
            else if (curve is NurbSpline nSpline)
            {
                curveCenter = nSpline.CtrlPoints.AveragePoint();
                XYZ tempDir = (nSpline.CtrlPoints.ElementAtOrDefault(1) - nSpline.CtrlPoints.ElementAtOrDefault(0)).Normalizex();
                normalDir = tempDir.CrossProduct(XYZ.BasisZ).CrossProduct(tempDir).Normalizex();
            }
            else// 其他情况，CylindricalHelix 尚未遇到过
            {
                curveCenter = curve.Center();
                normalDir = new XYZ();
            }
        }



    }
    /// <summary>
    /// 与绘制点、线相关的操作选项
    /// </summary>
    public class DrawOption
    {
        /// <summary>
        /// 绘制所依赖的文档对象，若指定了视图，该选项可为空
        /// </summary>
        public Document Document { get; set; }
        /// <summary>
        /// 指定绘制所在的平面视图，如果该选项不为空，则绘制详图组件
        /// </summary>
        public ViewPlan View { get; set; }
        /// <summary>
        /// 指定绘制所在的平面视图，如果该选项不为空，则绘制详图组件
        /// </summary>
        //public ViewPlan View { get; set; }

        /// <summary>
        /// 是否绘制用来表示线段方向的箭头
        /// </summary>
        public bool DrawLineArrow { get; set; }

        /// <summary>
        /// 是否在线段端点处绘制箭头
        /// </summary>
        public bool DrawArrowOnEnd { get; set; }

        /// <summary>
        /// 是否绘制简化后的闭合回路
        /// </summary>
        public bool SimplifyCurveloop { get; set; }

        /// <summary>
        /// 线段箭头的尺寸
        /// </summary>
        public double LineArrowScale { get; set; } = 0.2;

        /// <summary>
        /// 用于绘制点的圆形轮廓半径
        /// </summary>
        public double PointOutlineRadius { get; set; } = 0.1;

        /// <summary>
        /// 绘制坐标轴的长度
        /// </summary>
        public double AxisLength { get; set; } = 2;

        /// <summary>
        /// 绘制的线段、圆形轮廓线的样式
        /// </summary>
        public GraphicsStyle LineStyle { get; set; }
    }
}
